/**
 * 
 */
package com.googlecode.alfresco_caching_server.webscripts;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.extensions.webscripts.Description;
import org.springframework.extensions.webscripts.WebScript;
import org.springframework.extensions.webscripts.WebScriptException;

/**
 * @author dimyjeannot
 *
 */
public class ServerCacheConfig {
	
	private static Log logger = LogFactory.getLog(ServerCacheConfig.class);
	
	public static final String SERVER_CACHE_ROOT_ELEMENT_NAME = "cache";
	
	public static final String WEBSCRIPT_DESC_XML = ".desc.xml";
	
	public static final String CACHE_ID_EXTRACTOR = "cacheIdExtractor";
	
	public static final String CACHE_ID_EXTRACTOR_PARAMS = "cacheIdExtractorParams";
	
	public static Element cache = null;
	
	private boolean isCacheable = false;
	
    private WebScript script = null;
    
    private List<Element> uris = new ArrayList<Element>(0);
    
	/**
	 * 
	 */
	public ServerCacheConfig(InputStream serverCacheConfig, WebScript script) 
	{
		this.script = script;
		parseServerCacheConfig(serverCacheConfig);
	}
	
	public boolean isCacheable()
    {
        return this.isCacheable;
    }
	
	public String getCache()
    {
        return cache.asXML();
    }
	
	public String[] getURIs()
	{
		List<String> uris = new ArrayList<String>(this.uris.size());
		for(Element uri : this.uris){
		    uris.add(uri.asXML());
		}
		
		return uris.toArray(new String[uris.size()]);
	}
	
	private void parseServerCacheConfig(InputStream serverCacheConfig)
	{
		SAXReader reader = new SAXReader();
        logger.info("Found Cacheable Webscript. Checking if webcsript is properly configured");
        
        try
		{
        	isCacheable = true;
			Document document = reader.read(serverCacheConfig);
			Element rootElement = document.getRootElement();
			Description description = this.script.getDescription();
			List serverCacheElements = rootElement.elements(SERVER_CACHE_ROOT_ELEMENT_NAME);
			List urlElements = rootElement.elements("url");
			
			//Output
			String configPath = description.getId() + ".cache.xml";
            logger.info("Config Path " + configPath);
			
			//Find configured URIs
            if (urlElements == null || urlElements.size() == 0)
            {
                logger.error("Expected at least one <url> element in the Cache Config file. We will not include this webscript.");
                this.isCacheable = false;
                return;
            }

            Iterator iterElements = urlElements.iterator();
            while(iterElements.hasNext())
            {
                // Get URL element
                Element urlElement = (Element)iterElements.next();
                
                // Get URL value
                String template = urlElement.getTextTrim();

                // Get Cache ID Extractor
                String cacheIdExtractor = urlElement.attributeValue(CACHE_ID_EXTRACTOR);
                if (cacheIdExtractor == null || cacheIdExtractor.length() == 0)
                {
                	logger.error("The Cache ID Extractor is either missing or not properly configured for URL '" + template + "' We will not include this url.");
                	continue;
                }
                
                // Get Cache ID Extractor Params
                String cacheIdExtractorParams = urlElement.attributeValue(CACHE_ID_EXTRACTOR_PARAMS);
                if (cacheIdExtractorParams == null || cacheIdExtractorParams.length() == 0)
                {
                	logger.error("The Cache ID Extractor Params is either missing or not properly configured for URL '" + template + "' We will not include this url.");
                	continue;
                }
                
                Element uriTemplateElement = createURITemplateElement(urlElement);
                if( uriTemplateElement != null)
                {
                	this.uris.add(uriTemplateElement);
                }
                
            }
            
            if (this.uris.size() == 0)
            {
            	logger.error("No URLs have been properly configured for this webscript. We will not cache this webscript.");
            	this.isCacheable = false;
            }
			
			if (validateServerCacheElement(serverCacheElements)) 
	        {
				
				//Found a serverCache element, begin parsing
				Element serverCacheElement = (Element)serverCacheElements.get(0);
				
				//Validate against Schema that limits certain values
				if(validateXMLAgainstSchema(serverCacheElement))
				{
					cache = createEhcacheCacheElement(serverCacheElement, description.getDescPath());
				}
	        }
			else
			{
				this.isCacheable = false;
			}
			
			if (this.isCacheable)
			{
				logger.info("Webscript successfully configured for server caching: " + configPath);
			}
		}
		catch (DocumentException e)
		{
			logger.error("Failed to parse cache config. Excluding from list.", e);
		}
	}
	
	/**
     * Validate XML element for the server cache that will be used for parsing
     * 
     * @param reader SAXReader
     * @param servicedesc The service description
     * @return true if the root element is valid and the name matches
     */
    private boolean validateServerCacheElement (List serverCacheElements) 
    {
    	
		//Check if this Webscript is configured for Server Caching
        if (serverCacheElements == null)
        {
            return false;
        }
        
        //Check if only one serverCache element was passed
        if (serverCacheElements.size() > 1)
        {
            logger.error("Only one '" + SERVER_CACHE_ROOT_ELEMENT_NAME + "' element can be defined");
            return false;
        }
        else
        {
        	return true;
        }
        
    }
    
    /**
     * Validate passed values against the server cache xsd file
     * 
     * @param xml An XML file
     * @return true if server cache element is valid
     */
    private boolean validateXMLAgainstSchema(Element xml)
    {
        return true;
    }
    
    /**
     * Creates an Ehcache cache element
     * 
     * @return String representation of modified version of the ehcache.xml file
     */
    private Element createEhcacheCacheElement(Element serverCacheElement, String serviceDescPath) 
    {
    	Document ehcacheCacheSnippet = DocumentHelper.createDocument();
        Element ehcacheCacheElement = ehcacheCacheSnippet.addElement( "cache" );
        
        //Name the cache based on the descriptor path
		String cacheName = serviceDescPath.substring(0, serviceDescPath.lastIndexOf(WEBSCRIPT_DESC_XML));					
		ehcacheCacheElement.addAttribute("name", cacheName);
        
		Iterator serverCacheElementAttributes = serverCacheElement.attributeIterator();
		while(serverCacheElementAttributes.hasNext())
        {
            Attribute attribute = (Attribute)serverCacheElementAttributes.next();
            ehcacheCacheElement.addAttribute(attribute.getName(), attribute.getValue());
        }
		
		return ehcacheCacheElement;
    }
    
    /**
     * Creates a uri template element suitable for the cache server
     * 
     */
    private Element createURITemplateElement(Element urlElement) 
    {
    	Document uriTemplateSnippet = DocumentHelper.createDocument();
    	
    	Element uriTemplateElement = uriTemplateSnippet.addElement( "uri" );
    	
    	//Get and set template URL
    	String template = urlElement.getTextTrim();
    	uriTemplateElement.setText( template );
    	
    	//Get and set all attributes
    	Iterator uriTemplateAttributes = urlElement.attributeIterator();
    	while(uriTemplateAttributes.hasNext())
    	{
    		Attribute attribute = (Attribute)uriTemplateAttributes.next();
    		uriTemplateElement.addAttribute(attribute.getName(), attribute.getValue());
    	}
    	
    	return uriTemplateElement;
    }

}
